package com.pinfly.common.util;

import java.util.ArrayList;
import java.util.Iterator;

import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/** Provides some static methods useful for dealing with an XML DOM tree. */
public final class DOMUtil
{
    // String value for true
    private static final String TRUE = Boolean.TRUE.toString ();

    // Class shouldn't be instantiated
    private DOMUtil ()
    {
    }

    /**
     * Get all the element children of a node. This equivalent to calling
     * {@link #getNodesOfType getNodesOfType} and specifying a type of
     * <code>Node.ELEMENT_NODE</code>
     * 
     * @param node the DOM <code>Node</code> to search for child elements.
     * @return a <code>List</code> of the child elements. If there are none, an
     *         empty list is returned
     */
    public static java.util.List getElementChildren (Node node)
    {
        return getNodesOfType (node.getChildNodes (), Node.ELEMENT_NODE);
    }

    /**
     * Get child elements having a specified name. All children of the specified
     * node are examined; if the child is of type <code>Node.ELEMENT_NODE</code>
     * and it's name matches <code>name</code> it is included in the output
     * list.
     * 
     * @param node the DOM <code>Node</code> to search for child elements
     * @param name the nameof child elements to return.
     * @return a <code>List</code> of child elements of the specified type. If
     *         there are no child elements of that type, an empty list is
     *         returned.
     */
    public static java.util.List getNamedElementChildren (Node node, String name)
    {
        java.util.List allChildren = getNodesOfType (node.getChildNodes (), Node.ELEMENT_NODE);
        java.util.List namedChildren = new ArrayList ();
        for (Iterator it = allChildren.iterator (); it.hasNext ();)
        {
            Node n = (Node) it.next ();
            if (n.getNodeName ().equals (name))
            {
                namedChildren.add (n);
            }
        }
        return namedChildren;
    }

    /**
     * Get first child elements having a specified name. All children of the
     * specified node are examined; the first child of type
     * <code>Node.ELEMENT_NODE</code> whose name matches the specified name is
     * returned.
     * 
     * @param node the DOM <code>Node</code> to search for child elements
     * @param name the nameof child elements to return.
     * @return the first child that is an ELEMENT_NODE with the specified name.
     *         If there are no such children, returns <code>null</code>
     */
    public static Node getNamedElementChild (Node node, String name)
    {
        java.util.List allChildren = getNodesOfType (node.getChildNodes (), Node.ELEMENT_NODE);
        for (Iterator it = allChildren.iterator (); it.hasNext ();)
        {
            Node n = (Node) it.next ();
            if (n.getNodeName ().equals (name))
            {
                return n;
            }
        }
        return null;
    }

    /**
     * Search a NodeList for nodes of a particular type.
     * 
     * @param nodes the <code>NodeList</code> to search
     * @param nodeType the type of child elements to return. One of the valid
     *            types defined in <code>org.w3c.dom.Node</code>
     * @return a <code>List</code> of <code>Node</code>s of the specified type.
     *         If there are none, an empty list is returned.
     */
    public static java.util.List getNodesOfType (NodeList nodes, int nodeType)
    {
        java.util.List l = new ArrayList ();
        for (int i = 0; i < nodes.getLength (); i++)
        {
            Node n = nodes.item (i);
            if (n.getNodeType () == nodeType)
            {
                l.add (n);
            }
        }
        return l;
    }

    /**
     * Retrieve an attribute value from a node.
     * 
     * @param n the Node
     * @param attrName the name of the desired attribute
     * @return the attribute value; returns <code>null</code> if the node does
     *         not contain the attribute.
     */
    public static String getAttributeValue (Node n, String attrName)
    {
        String value = null;

        NamedNodeMap attrs = n.getAttributes ();
        Node a = attrs.getNamedItem (attrName);
        if (a != null)
        {
            value = a.getNodeValue ();
        }
        return value;
    }

    /**
     * Retrieve an integer attribute value from a node.
     * 
     * @param n the Node
     * @param attrName the name of the desired attribute
     * @return the value of the attribute, as an <code>int</code>
     * @throws NumberFormatException if the node does not contain the attribute,
     *             or it can't be converted to an int
     */
    public static int getIntAttributeValue (Node n, String attrName) throws NumberFormatException
    {
        String val = getAttributeValue (n, attrName);
        return Integer.parseInt (val);
    }

    /**
     * Retrieve a float attribute value from a node.
     * 
     * @param n the Node
     * @param attrName the name of the desired attribute
     * @return the value of the attribute, as a <code>float</code>
     * @throws NumberFormatException if the node does not containt the
     *             attribute, or it can't be converted to a float
     */
    public static float getFloatAttributeValue (Node n, String attrName) throws NumberFormatException
    {
        String val = getAttributeValue (n, attrName);
        return Float.parseFloat (val);
    }

    /**
     * Retrieve a boolean attribute value from a node.
     * 
     * @param n the Node
     * @param attrName the name of the desired attribute
     * @return <code>true</code> if the attribute exists and its value is equal
     *         (ignoring case) to the string returned from
     *         <code>Boolean.TRUE.toString()</code>. If the attribute doesn't
     *         exist, or has any other value, returns <code>false</code>
     */
    public static boolean getBooleanAttributeValue (Node n, String attrName)
    {
        String val = getAttributeValue (n, attrName);
        return TRUE.equalsIgnoreCase (val);
    }

}
